Глава 15

ВВЕДЕНИЕ В TURBO VISION

В этой главе мы попробуем разработать программу, которая использует некоторые возможности Turbo Vision. Пусть, например, нам необходимо создать простейшую информационную систему - нечто вроде электронной записной книжки. Предполагается, что данные, используемые этой системой, будут храниться в виде записей в дисковом файле. Наша задача - разработать удобную диалоговую программу, облегчающую доступ к файловым данным.

Разработка программы, разумеется, не является самоцелью - ведь для нас это только повод для конкретного знакомства с Turbo Vision. Поэтому мы будем создавать программу постепенно, каждый раз фиксируя достигнутые результаты. Если Вас интересует собственно информационная программа, используйте ее окончательный вариант, приведенный в прил.П.5.4

15.1. ПРОСТЕЙШАЯ ПРОГРАММА В TURBO VISION

Работа большинства прикладных программ проходит в три этапа: подготовка к работе, собственно работа и, наконец, ее завершение. В нашем случае к подготовительному этапу можно отнести такие действия, как анализ существования файла данных и его (файла) подготовка к работе. На этапе завершения мы должны обеспечить необходимые действия по сохранению файла/Все остальные действия относятся к среднему этапу. С учетом этого можно написать следующую простейшую программу:

begin

{Подготовить работу программы} 

{Выполнить необходимые действия} 

{Завершить исполнение программы} 

end.

Если Вы попытаетесь выполнить эту программу, ничего не произойдет - ведь мы еще никак не конкретизировали необходимые действия. Так обстоит дело в Турбо Паскале, но не так - в Turbo Vision! Для любой прикладной программы Turbo Vision сразу же создает некоторую минимальную программную реализацию, которую Вы можете затем постепенно наращивать в ходе детализации программы. Вот начальный вариант программы с использованием Turbo Vision:

Uses Арр; {Используется модуль АРР библиотеки Turbo Vision}

var

Notebook: TApplication;

begin

Notebook.Init; {Подготовить работу программы} 

Notebook.Run; {Выполнить необходимые действия} 

Notebook.Done {Завершить исполнение программы}

end.

В этой программе объявляется использование стандартного для Turbo Vision модуля Арр (от application - приложение, прикладная программа). Такое объявление открывает доступ прикладной программе к мощным возможностям Turbo Vision. Чтобы использовать эти возможности, мы объявили переменную Notebook (notebook - записная книжка) типа TApplication. Как Вы вскоре заметите, на букву Т в Turbo Vision начинаются идентификаторы объектов. Таким образом, Notebook - это экземпляр объекта TApplication, т.е. объединение данных (полей) и методов обработки этих данных (процедур, функций, конструкторов, деструкторов). В объекте TApplication предусмотрены методы Init, Run и Done. Вызов этих методов и составляет исполняемую часть нашей программы. Если Вы подготовите и запустите программу, на экране ПК появится изображение, показанное на рис. 15.1.

Рис.15.1. Вид экрана для простейшей программы

Для выхода из программы необходимо, как это следует из надписи в левом нижнем углу экрана, нажать Alt-X или подвести к этой надписи указатель мыши (если, разумеется, Ваш ПК оснащен этим устройством) и нажать ее левую кнопку.

Как видите, даже простейшая программа «знает», как создать экран, распознает команду Alt-X и может работать с мышью. Совсем не плохо для трех исполняемых операторов, не так ли? Такие возможности доступны потому, что в объекте TApplication предусмотрены соответствующие методы. В этом смысле использование объектов напоминает использование подпрограмм из библиотек. Однако в отличие от подпрограммы любой объект имеет все необходимые ему данные. Говоря об объектах, я часто буду использовать такие слова, как «знает», «умеет», «может», подчеркивая тем самым главную отличительную особенность объектов от традиционных подпрограмм - их «разумность»: последовательное проведение в жизнь принципа инкапсуляции (объединения) данных и всех необходимых для их обработки методов придает объекту определенную независимость от других элементов программы; объекты как бы «живут» в программе своей независимой жизнью.

Простейшая программа не может выполнять никаких других действий, кроме уже перечисленных, так как именно эти действия запрограммированы в методах Init и Run объекта TApplication. В ходе их выполнения на экране создается изображение, имеющее три зоны: верхняя строка, нижняя строка и вся остальная часть экрана. Верхняя строка обычно используется для размещения опций главного меню (не забывайте, что Turbo Vision - это оболочка для диалоговых программ!). Нижняя строка - строка статуса: в ней указываются так называемые командные клавиши, т.е. клавиши или комбинации клавиш, которые вызывают нужные действия без перехода к промежуточному диалогу. Вся остальная часть экрана составляет «рабочий стол» программы - сюда будут помещаться сообщения, здесь будут размещаться окна, «выпадающие» меню (меню нижнего уровня) и т.п.

15.2. ФОРМИРОВАНИЕ СТРОКИ СТАТУСА

Стандартный вид экрана, показанный на рис. 15.1, можно изменять. Попробуем придать ему некоторый специфический для нашей программы вид. Например, заменим в строке статуса стандартное сообщение

Alt-X Exit 

на русифицированное

Alt-X Выход

Таким образом, нам необходимо модифицировать стандартное поведение объекта Notebook. Для этого мы должны отыскать в типе TApplication метод, ответственный за создание строки статуса. Если мы обратимся к прил.П6, то обнаружим, что объект типа TApplication содержит методы Init и Done, с помощью которых создаются и уничтожаются экземпляры объекта, но в нем нет метода, ответственного за строку статуса. Однако из таблицы наследования нетрудно определить, что этот метод (InitStatusLine) он наследует от своего родителя TProgram. Как изменить работу метода? В рамках объектно-ориентированной библиотеки для этого поступают следующим образом: объявляется объект-потомок от стандартного объекта, поведение которого необходимо изменить, и в новом объекте описывается свой метод, ответственный за это поведение.

Изменим программу следующим образом;

Uses Арр, Objects, Menus, Drivers, Views; 

type

TNotebook = object (TApplication) {Создаем объект-потомок от TApplication} 

Procedure InitStatusLine; Virtual; {Перекрываем старый метод InitStatusLine новым} 

end;

{-----------}

Procedure TNotebook. InitStatusLine;

{Описание нового метода, с помощью которого создается строка статуса}

var

R: TRect; {Границы строки статуса} 

begin

GetExtent (R) ; {Получаем в R координаты всего экрана} 

R.A.Y := pred(R.B.Y) ; {Помещаем в R координаты строки статуса}

{Создаем строку статуса:} 

StatusLine := New(PStatusLine, Init(R, 

{Определяем один вариант строки статуса:} 

NewStatusDef (0, $FFFF, {Устанавливаем для этого варианта максимальный диапазон контекстной справочной службы}

{Определяем единственную клавишу Alt-X: } 

NewStatusKey('~Alt-X~ Выход' , kbAltX, cmQuit, 

NIL), {Нет других клавиш} 

NIL) {Нет других строк статуса}

))

end; {TNotebook. InitStatusLine}

{---------}

var

Notebook: TNotebook;{Изменен тип переменной!}

begin

Notebook. Init;

Notebook. Run;

Notebook. Done 

end .

Как видим, программа сразу же усложнилась. Во-первых, в ней используются идентификаторы, которые определены в других модулях Turbo Vision, - эти модули мы перечислили в предложении Uses. Во-вторых, нам потребовалось объявить новый объект TNotebook как потомок от объекта TApplication. Объект-потомок наследует от своего объекта-родителя все поля и методы и при необходимости может их дополнять своими полями и методами, а также перекрывать методы родителя. Как раз для того, чтобы перекрыть унаследованный от TProgram стандартный метод InitStatusLine, ответственный за создание строки статуса, нам и понадобилось объявление нового типа TNotebook. Строка

Procedure InitStatusLine; Virtual;

в объявлении этого типа указывает, что новый объект будет пользоваться одноименным, но иным, чем объект-родитель, методом. Возможность замены методов на одноименные, но с другим содержанием называется полиморфизмом.

Процедура TNotebookJnitStatusLine раскрывает суть нового метода. В ней используется обращение к методам NewStatusDef vi NewStatusKey, с помощью которых создается динамический объект типа TStatusLine. Программа TApplication обращается к методам этого объекта для обслуживания строки статуса.

Turbo Vision позволяет определять несколько вариантов строки статуса. Каждый вариант создается с помощью метода NewStatusDef. В зависимости от текущего состояния программы (от контекста программы) Turbo Vision автоматически помещает в строку статуса нужный вариант. Так как в нашей программе используется единственный вариант строки статуса, мы указали максимально возможный диапазон контекста программы при обращении к методу NewStatmDef.

С помощью метода NewStatusKey в строке статуса определяется очередная командная клавиша. При обращении к методу сначала указывается текст, высвечиваемый в строке статуса, причем символом «~» выделяется та часть сообщения, которая будет подсвечена в строке статуса другим цветом: таким способом в Turbo Vision указываются командные клавиши. Идентификатор kbAltX задает комбинацию клавиш, а cmQuit - связанную с ней команду.

В программе объявляется переменная R типа TRect. С помощью такого типа переменных программист задает координаты прямоугольного участка экрана. Эта переменная необходима нам для указания того места на экране, где будет помещено сообщение

Alt-X Выход

определяющее командные клавиши Alt-X. Для правильного задания этих координат мы использовали два предложения:

GetExtent(R);

R.A.Y := pred(R.B.Y);

В первом вызывается стандартный метод Turbo Vision GetExtent, с помощью которого в R помещаются координаты доступной в данный момент части экрана. Во втором - номер той строки (строки статуса), куда будет выводиться сообщение. В Turbo Vision тип TRect объявляется в виде следующей записи:

type

TRect = record

A: record {Координаты верхнего левого угла} 

X: Byte; Y: Byte 

end;

В: record {Координаты правого нижнего угла} 

X: Byte; Y: Byte 

end 

end;

Таким образом, второе предложение лишь уменьшает на единицу вертикальную координату самой нижней доступной строки и устанавливает полученное значение в поле R.A. Y (это поле задает вертикальную координату верхнего левого угла прямоугольного участка). Заметим, что в Turbo Vision минимальные координаты задаются значением 0, в то время как в стандартном модуле CRT Турбо Паскаля минимальные координаты имеют значение 1.

Обратите внимание на характерный прием, широко используемый в Turbo Vision: при обращении к методам NewStatusDef и NewStatusKey последним параметром указывается переменная типа Pointer. Внутри методов эта переменная трактуется как ссылка на новый метод, что позволяет организовать цепочку последовательных определений. Вложенная последовательность вызовов заканчивается зарезервированной константой NIL, указывающей на конец цепочки. Если бы мы, например, захотели добавить в строку статуса определение клавиши F10, связав ее с закрытием активного окна, мы могли бы использовать такую конструкцию:

NewStatusDef(0, $FFFF,

NewStatusKey('~Alt-X~ Выход', kbAltX, cmQuit,

NewStatusKey('~F10~ Закрыть окно', kbFl0, cmClose,

NIL)), {Нет других клавиш} 

NIL) {Нет других определений}

Описанный пример позволяет нам сделать очень важный вывод:

Чтобы модифицировать стандартное поведение объекта, необходимо создать объект-потомок от этого объекта и перекрыть в нем нужный метод.

15.3. ФОРМИРОВАНИЕ МЕНЮ

Вернемся к смысловой части нашего примера и подумаем о том, какие еще свойства следует придать программе. Поскольку мы предполагаем работу с файлом, можно включить в программу код, реализующий строку меню с опцией «Файл», связав с этой опцией такие действия, как открытие уже существующего файла данных и/или создание нового. Здесь же можно предусмотреть возможность альтернативного выхода из программы. Кроме того, в главное меню следует поместить еще одну опцию, назовем ее «Работа». Эта опция должна открыть доступ к содержательной части программы. С учетом сказанного программу нужно дополнить следующими строками:

const

{Команды для обработчиков событий:}

cmWork = 203; {Обработать данные}

cmDOS = 204; {Временно выйти в ДОС}

WinComl: TCommandSet = [cmSave,cmWork]; {Множество временно недоступных команд}

Эти строки следует вставить сразу после предложения Uses; они определяют коды команд, которые будут затем использоваться для вызова соответствующих частей программы. Кроме того, объявление объекта TNotebook нужно дополнить строкой

type

TNotebook = object (TApplication)

.......

Procedure InitMenuBar; Virtual; {Перекрываем стандартный метод InitMenuBar}

end;

в которой перекрывается прежний метод InitMenuBar, ответственный за формирование строки меню. И, наконец, в разделе объявлений программы следует поместить описание метода InitMenuBar и видоизменить описание метода InitStatusLine:

Procedure TNotebook.Ini tMenuBar; 

{Создание верхнего меню}

var

R: TRect; 

begin

GetExtent (R) ;

R.B.Y := succ(R.A.Y) ; {R - координаты, строки меню} 

MenuBar := New ( PMenuBar , Init(R, NewMenu ( {Создаем меню}

{Первый элемент нового меню представляет собой подменю (меню второго уровня) . Создаем его} NewSubMenu( '~F~/ Файл', hcNoContext, 

{Описываем элемент главного меню} 

NewMenu ( {Создаем подменю} 

NewItem( {Первый элемент} 

'~1~/ Открыть ', 'F3' , kbF3,cmOpen, hcNoContext, 

NewItem( {Второй элемент} 

'~2~/ Закрыть ', 'F2', kbF2, cmSave,hcNoContext, 

NewItem( {Третий элемент}

'~3~/ Сменить диск' , ' ' , 0, cmChangeDir,hcNoContext, 

NewLine ( {Строка-разделитель} 

NewItem('~4~/ Вызов ДОС' , ' ' , 0, cmDOSShell,

hcNoContext, 

NewItem('~5~/ Конец работы' , 'Alt-X' ,

kbAltX, cmQuit, hcNoContext,

NIL) ) ) ) ) ) {Нет других элементов подменю} ),

{Создаем второй элемент главного меню} 

NewItem( '~W~/ Работа', ' ', kbF4, cmWork, hcNoContext, 

NIL) {Нет других элементов главного меню} )))) 

end; {TNotebook. InitMenuBar}

{---------}

Procedure TNotebook. InitStatusLine;

{Формирует строку статуса} 

var

R: TRect; {Границы строки статуса} 

begin

GetExtent (R,) ; {Получаем в R координаты всего экрана} 

R.A.Y := pred(R.B.Y) ; StatusLine := New(PStatusLine,

Init(R, {Создаем строку статуса} 

NewStatusDef (О, $FFFF, {Устанавливаем максимальный диапазон контекстной справочной службы}

NewStatusKey('~Alt-X~ Выход', kbAltX, cmQuit, 

NewStatusKey('~F2~ Закрыть', kbF2, cmSave, 

NewStatusKey('~F3~ Открыть', kbF3,cmOpen,

NewStatusKey('~F4~ Работа', kbF4,cmWork, 

NewStatusKey('~F10~ Меню', kbF10,cmMenu, NUL))))),{Нет других клавиш} 

NUL){Нет других определений}

));

DisableCommands(WinComl) {Запрещаем недоступные команды} 

end; {TNotebook.InitStatusLine}

В новом варианте программы мы продвинулись дальше по пути конкретизации ее действий. Если Вы запустите программу и нажмете клавиши Alt-F (вызов опции «Файл» главного меню), на экране появится изображение, показанное на рис. 15,2.

Рис.15.2. Вид окна с развернутым меню опции Файл

Определение опций меню во многом напоминает определение командных клавиш в строке статуса. Отличие заключается лишь в том, что с любой опцией меню может быть при необходимости связана встроенная справочная служба. В нашей программе мы не используем эту возможность, для чего задаем стандартный идентификатор hcNoContext (нет контекстно-зависимой справки) при описании каждой опции.

Подобно клавишам строки статуса командные клавиши меню выделяются символом «~». Заметим, что не имеет смысла назначать в качестве командных клавиш клавиши кириллицы, так как при их анализе Turbo Vision игнорирует коды 128...255. Если бы, например, мы задали в качестве командной клавиши для опции «Файл» клавишу «Ф», нажатие Alt-Ф не вызвало бы развертывания подменю, связанного с этой опцией (как и в Турбо Паскале, в Turbo Vision опции главного меню вызываются комбинацией А1t-<клавиша>, а опции меню нижнего уровня - просто нажатием нужной командной клавиши).

15.4. КОМАНДЫ

Необходимо пояснить назначение вновь введенных констант стХХХХ. Это так называемые команды, точнее их коды (шифр). Сразу же замечу, что префикс cm в идентификаторах команд не является следствием каких-либо требований со стороны Turbo Vision, просто он принят для предопределенных (стандартных) команд, таких как cmQuit и cmClose. Вновь вводимые команды не являются предопределенными, при их описании я заимствовал стандартный префикс только по соображениям стилистики.

Что же такое команды Turbo Vision? Внимательный анализ предыдущего варианта программы показывает, что.эти коды еще никак не используются, они понадобились лишь для синтаксически правильного обращения к стандартным методам инициации строк меню и статуса. В новом варианте программной реализации Вы можете вызвать любую опцию главного меню или нажать любую командную клавишу - это не приведет ни к каким последствиям: пока работает только команда Alt-X, завершающая работу программы, и клавиши F2, F3 и F10. Происходит это потому, что эти клавиши мы связали со стандартными командами cmQuit, cmSave, cmOpen и стМепи и обрабатываются они где-то внутри Turbo Vision. Новые команды не известны системе, и их обработку мы должны взять на себя.

Как мы увидим дальше, в Turbo Vision есть средства контроля командных клавиш. Эти средства определяют факт нажатия на клавишу с помощью генерации кода соответствующей команды, который (код) будет в этом случае передан нашей программе. Таким образом, обработка команд заключается в расшифровке получаемых от Turbo Vision кодов и передаче управления соответствующим частям программы.

Для шифровки команд в Turbo Vision используется 16-разрядное слово, что позволяет определить до 65535 различных команд. Некоторые из этих кодов зарезервированы для использования внутри Turbo Vision, остальные доступны программисту:

Код команды

Зарезервировано

Можно запретить

0. . .99

Да

Да

100.. .255

Нет

Да

256. . .999

Да

Нет

1000. . .65535

Нет

Нет

Команды с кодами от 0 до 255 при необходимости могут быть временно запрещены, остальные команды запретить нельзя - вот почему используется два диапазона доступных для программиста кодов команд. Временное запрещение команд связано с тем очевидным свойством диалоговых программ, что отнюдь не любая команда может исполняться в каждом возможном состоянии программы. Например, бессмысленно использовать команду «Закрыть файл», если файл еще не открыт. Наоборот, если файл уже открыт, команда «Открыть файл» может стать временно недоступной пользователю. Механизм маскирования (временного запрещения) команд позволяет избавиться от многочисленных проверок контекстуальной корректности тех или иных команд: программист может их запретить впредь до наступления какого-либо события, а запрещенные команды игнорируются средствами Turbo Vision и в программу пользователя не передаются.

В нашей программе имеет смысл запретить команды cmSave и cmWork до тех пор, пока пользователь не откроет нужный файл с данными. Запрет команд достигается обращением к стандартной процедуре DisableCommands (см. предыдущий вариант программы). Указанные в обращении к ней команды задаются в виде множества кодов (мощность любого множества в Турбо Паскале не может превышать 256, вот почему могут быть запрещены только первые 256 команд) и становятся недоступны впредь до обращения к процедуре EnableCommands (разрешить команды).

Запрещенные опции меню (как и временно недоступные командные клавиши) выделяются на экране оттенком (пониженной яркостью).

15.5. СОБЫТИЯ И ИХ ОБРАБОТКА

Весьма важным принципом Turbo Vision является принцип отделения процесса создания видимых изображений от процесса обработки данных. Это означает, что все действия по созданию разнообразных окон, меню и прочих видимых элементов можно осуществлять, не заботясь о тех командах (действиях пользователя), которые будут связаны с ними. Именно так мы поступили при определении меню и строки статуса -коды команд дают возможность распознать соответствующие действия пользователя, однако сами эти действия пока еще никак не раскрыты. И наоборот, мы можем разрабатывать части программы, ответственные за обработку действий пользователя, не связывая прямо эти части с созданием нужных видимых элементов.

Turbo Vision поддерживает два возможных способа действия пользователя - с помощью клавиш клавиатуры и с помощью мыши. Любое такое действие пользователя с точки зрения Turbo Vision приводит к появлению события, т.е. к созданию небольшого информационного пакета, описывающего вновь возникшую ситуацию. События распространяются от одной части программы к другой до тех пор, пока не обнаружится подпрограмма, ответственная за обработку данного события. Эта подпрограмма обычно очищает информационный пакет и таким образом блокирует дальнейшее перемещение события.

Пожалуй, именно механизм событий кардинально отличает Turbo Vision от других библиотек Турбо Паскаля. На первых порах это может вызвать определенные трудности, связанные с отладкой программ. Принцип независимости обработки событий от процесса создания видимых элементов приводит фактически к появлению двух параллельных процессов в рамках одной программы: процесса создания видимых элементов и процесса обработки событий. Говоря о программах Turbo Vision, следует помнить, что эти программы управляются событиями. Их трассировка (прослеживание работы) в среде Турбо Паскаль обычно достигается установкой и использованием контрольных точек.

Подпрограммы, ответственные за обработку действий пользователя, называются обработчиками событий. Любой стандартный для Turbo Vision объект, обеспечивающий создание видимого элемента, имеет собственный обработчик событий (виртуальный метод HandleEvent), который Вы можете перекрыть своим собственным методом, если Вас не устраивает стандартная реакция объекта на то или иное событие. Существует такой метод и в объекте TNotebook. По умолчанию этот объект использует обработчик событий, унаследованный им от объекта-родителя TApplication. Стандартный обработчик знает, как реагировать на команды cmQuit и стМепи, но ему не известны новые команды cmWork, cmOpenFile и другие. Чтобы программа смогла правильно обработать эти команды, мы должны перекрыть стандартный метод HandleEvent объекта TNotebook новым. Добавим в описание объекта TNotebook еще одну строку

type

TNotebook = object (TApplication)

.......

Procedure HandleEvent(var Event: TEvent); Virtual; 

end;

и поместим в раздел объявлений текст новой подпрограммы:

Procedure TNotebook.HandleEvent(var Event: TEvent); 

{Обработчик событий программы} 

begin {TNotebook.HandleEvent} 

Inherited HandleEvent(Event);{Обработка стандартных команд cmQuit и cmMenu}

if Event.What = evCommand then 

case Event.Command of

{Обработка новых команд:}

cmOpen : FileOpen; {Открыть файл}

cmSave:FileSave; {Закрыть файл}

cmChangeDir:ChangeDir; {Сменить диск}

cmDOSShell:DOSCall; {Временный выход в ДОС}

cmWork:Work; {Обработать данные} 

else

exit {Не обрабатывать другие команды} 

end;

ClearEvent (Event) {Очистить событие после обработки}

end; {TNotebook.HandleEvent}

Чтобы новый вариант программы можно было выполнить, следует предусмотреть «заглушки» для несуществующих пока процедур FileOpen, FileSave и т.д. Например:

Procedure FileOpen;

begin

end;

Поведение вновь созданного варианта программы внешне ничем не отличается от предыдущего: также будут созданы меню, строка статуса и основное поле экрана, программа по-прежнему будет распознавать команды Alt-X и F10. Однако теперь она будет реагировать и на новые команды. Чтобы убедиться в этом, установите контрольные точки в заглушках FileOpen и FileSave и запустите программу вновь: нажатие на клавишу F3 вызовет останов в контрольной точке FileOpen - ведь именно с этой клавишей мы связали команду cmOpen в процедуре InitStatusLine, в то время как нажатие на клавишу F2 не приведет к срабатыванию контрольной точки FileSave, поскольку команда cmSave пока еще запрещена и обработчик HandleEvent ее просто не «увидит».

Чтобы использовать нестандартные команды меню или строки статуса, мы должны перекрыть обработчик событий программы, в новом обработчике выделить из потока событий команды и распознать их коды.

Чтобы стали более понятны действия обработчика событий, отметим, что тип TEvent в Turbo Vision определен как запись такого вида:

type

TEvent = record 

What: Word; {Определяет тип события} 

case Word of {"Пустое" событие} 

evMouse: ( {Событие от мыши:} 

Buttons: Byte; {Состояние кнопок} 

Double: Boolean;{Признак двойного нажатия кнопки мыши}

Where: TPoint); {Координаты курсора мыши} 

evKeyDown: ( {Событие от клавиатуры:} 

case Integer of

0: (KeyCode: Word);{Код клавиши} 

1: (CharCode: Byte; ScanCode: Byte)); 

evMessage: ( {Событие-сообщение:} 

Command: Word; {Код команды} 

case Word of

0:(InfoPtr: Pointer); 

1:(InfoLong: Longlnt); 

2:(InfoWord: Word); 

3:(Infolnt: Integer);

4:(InfoByte:Byte);

5:(InfoChar:Char));

end;

Стандартная маска evCommand позволяет выделить из потока событий только те, которые связаны с передачей команд между различными обработчиками событий. Именно таким способом стандартный обработчик TApplication.HandleEvent сообщает новому обработчику TNotebookHandleEvent о возникновении события, связанного с вновь определенной командой. Если бы мы не предусмотрели вызов стандартного обработчика с помощью оператора

Inherited HandleEvent(Event);

нам пришлось бы самим анализировать положение мыши или нажатую клавишу и интерпретировать их как соответствующие команды. Включение вызова TApplication.HandleEvent в тело нашего обработчика событий избавляет нас от этой рутинной работы.

В конце обработчика мы вызвали стандартную процедуру ClearEvent, с помощью которой в переменную Event помещается сообщение Nothing («пустое» событие). Это событие игнорируется всеми обработчиками, так что программа будет повторять проверку состояния мыши и клавиатуры до тех пор, пока не произойдет нового события. Фактически тело процедуры TApplication.Run (см. раздел исполняемых операторов нашей программы) состоит из бесконечно повторяющегося цикла проверки мыши и клавиатуры и передачи событий по цепи обработчиков событий. После получения любого события обработчик должен либо обработать это событие и очистить переменную Event, либо просто вернуть управление обработчику верхнего уровня, если эта команда не предназначена для него, либо, наконец, сформировать и передать новое событие для реализации команд, которые распознаны им, но которые он выполнять не умеет или не должен.

15.6. ПРОГРАММИРОВАНИЕ ДИАЛОГОВЫХ ЗАПРОСОВ

В обработчике событий TNotebook.HandleEvent мы предусмотрели вызовы нескольких процедур, с помощью которых реализуются конкретные действия программы. Настала пора запрограммировать эти действия.

Начнем с процедуры FileOpen. Ее задача - выбрать один из возможных файлов с данными и подготовить его к работе. Конечно, программу можно было бы сделать менее гибкой, раз и навсегда «привязав» ее к какому-то одному файлу, скажем, с именем notebook.dat. Но даже и в этом случае следует решить проблему с местоположением файла данных, а также определить, что должна делать программа, если нужный файл не найден. Наша программа будет весьма гибкой в этом отношении: она позволит указать интересующий нас файл мышью или клавишами курсора, либо ввести имя файла с помощью клавиатуры или взять его из буфера ранее введенных имен. Иными словами, поведение нашей программы будет в точности повторять поведение среды Турбо Паскаль в момент нажатия на клавишу F3.

Если Вы когда-либо программировали подобные действия в Турбо Паскале, Вы по достоинству оцените простоту их реализации в Turbo Vision:

Procedure FileOpen; {Открывает файл данных} 

var

PF: PFileDialog; {Диалоговое окно выбора файла} 

Control: Word; 

s: PathStr; 

begin

{Создаем экземпляр динамического объекта:} 

New(PF, Init('*.dat','Выберите нужный файл:','Имя файла',fdOpenButton,0)); 

{С помощью следующего оператора окно выводится на экран и результат работы пользователя с ним помещается в переменную Control:} 

Control := DeskTop^.ExecView(PF); 

{Анализируем результат запроса:} 

case Control of

StdDlg. cmFileOpen, cmOk: 

begin {Пользователь указал имя файла:}

PF^.QetFileName(s) ; {s содержит имя файла}

{-----------} {Открыть файл}

end;

end; {case Control} 

Dispose (PF, Done) {Уничтожаем экземпляр} 

end; {FileOpen}

Для реализации этого фрагмента необходимо указать имя модуля StdDlg в предложении Uses - в этом модуле описан тип PFileDialog и предусмотрены все необходимые методы для работы с ним. Кроме того, в программе используется переменная S типа PathStr. Этот тип описан в модуле DOS - сошлитесь также и на него. Сделайте нужные изменения в тексте программы, не раскрывая пока сущности действий

{Открыть файл}

запустите программу на счет и нажмите клавишу F3 - экран приобретет вид, показанный на рис.15.3.

Тип PFileDialog - это указатель на объект TFileDialog, создающий и обслуживающий стандартное диалоговое окно выбора файлов. Все действия по созданию и использованию диалогового окна, показанного на рис.15.3, реализуются двумя операторами:

NewfPF, Init('*.dat','Выберите нужный файл:',

'Имя файла',fdOpenButton, 0)); 

Control := DeskTopA.ExecView(PF);

Puc. 15.3. Диалоговое окно выбора файлов

Первый оператор инициирует новый экземпляр объекта TFileDialog. Три строковых параметра обращения к конструктору Init этого объекта задают, соответственно, маску выбираемых файлов ('*.dat'), заголовок диалогового окна ('Выберите нужный файл:') и заголовок окна ввода ('Имя файла'). Параметр fdOpenButton указывает на необходимость включить в диалоговое окно кнопку Open. Последним параметром задается идентификатор протокола ввода. Доступ к этому протоколу открывается кнопкой [|] справа от окна ввода. Сам протокол хранится в куче в виде последовательности вводившихся ранее текстовых строк. Идентификатор протокола ввода позволяет при необходимости использовать один и тот же протокол в разных диалоговых окнах.

Второй оператор

Control := DeskTop^.ExecView(PF);

помещает вновь созданное окно в основное поле экрана программы (ссылка DeskTop^) и инициирует диалог с пользователем. Результат диалога возвращается в переменной Control, значение этой переменной анализируется оператором

case Control of

.......

end;

Если Control содержит коды команд cmOk или cmFileOpen, то с помощью метода GetFileName объекта TFileDialog в переменную S записывается полное имя файла (с предшествующим путем).

В методе TFileDialog.GetFileName (var Name: Pathstr) параметр обращения должен иметь тип PathStr. Этот тип определен в модуле DOS,- вот почему нам понадобилось сослаться на этот модуль в предложении Uses. Если указать компилятору на необходимость смягчить проверку строковых типов (директива компилятора {$V-}), то при обращении к GetFileName можно использовать переменнуюлюбого строкового типа, в том числе String.

Перед выходом из процедуры FileOpen экземпляр объекта TFileDialog уничтожается (удаляется из кучи) обращением к деструктору Done.

По описанной схеме в Turbo Vision создаются и используются любые другие диалоговые окна.

Для реализации диалогового запроса необходимо создать диалоговое окно и с помощью функции ExecView объекта-владельца (программы) инициировать диалог с пользователем. Результат, возвращаемый этой функцией, будет содержать выбранную пользователем команду.

Чтобы запрограммировать действия, связанные с открытием файла, следует вначале решить, какие именно данные он будет содержать. Напомню, что мы разрабатываем диалоговую программу управления «записной книжкой». Структура типичной записи в такой книжке состоит из трех полей: имя, телефон, адрес. Учитывая это, будем считать, что данные в файле хранятся в виде следующих записей:

const

LName = 25;{Длина поля Name}

LPhone= 11;{Длина поля Phone}

LAddr =40;{длина поля Addr}

type

DataType = record {Тип данных в файле}

Name : String[LName]; {Имя}

Phone: String[LPhone] {Телефон} 

Addr : String[LAddr] {Адрес}

end;

Поместим эти строки в начале программы, а перед описанием процедуры FileOpen вставим определения следующих глобальных переменных:

var

DataFile: file of DataType; {Файловая переменная} 

OpFileF : Boolean; {Флаг открытого файла}

Дополним текст процедуры FileOpen такими строками:

case Control of

StdDlg.cmFileOpen,cmOk: 

begin

PFA.GetFileName(s);

Assign(DataFile,s); {Отсюда начинаются новые строки}

{$I-}

Reset(DataFile);

if lOResult <> 0 then

Rewrite{DataFile); OpFileF := IOResult=0; 

{$I+}

if OpFileF then 

begin

DisableCommands(WinCom2);

EnableCommands(WinComl)

end

end; 

end;

С помощью оператора DisableCommands мы временно запрещаем набор команд, указанный в константе WinComl. Эта константа в нашем случае должна содержать команду стОреп; ее определение нужно включить сразу за определением константы WinComl:

const

WinComl: TCommandSet = [cmSave, cmWork]; 

WinCom2: TCommandSet = [cmOpen];

Обращение к процедуре EnableCommands разрешает использовать команды cmSave vicmWork.

15.7. ИНКАПСУЛЯЦИЯ НОВЫХ ПОЛЕЙ И МЕТОДОВ

При попытке откомпилировать полученный вариант программы Турбо Паскаль сообщит о неизвестном идентификаторе DisableCommands. На первый взгляд это кажется странным - ведь аналогичное обращение в обработчике событий TNotebook.HandleEvent не вызывало проблем! Все дело в том, что мы работаем с объектами, а следовательно, здесь очень важным становится контекст программ. Обработчик TNotebook-HandleEvent - это метод объекта TNotebook, который унаследовал от своих родителей многие свойства, в том числе и метод DisableCommands. Процедура FileOpen не является потомком объектов Turbo Vision и не имеет доступа к их полям и методам.

В Turbo Vision все новые процедуры обычно инкапсулируются в объекты., если в них необходимо получить доступ к специфическим средствам этих объектов.

Поскольку процедура FileOpen вызывается из обработчика событий объекта TNotebook, нам следует включить ее в виде нового метода этого объекта:

type

TNotebook = object(TApplication)

.......

Procedure FileOpen; 

Procedure FileSave; 

Procedure ChangeDir; 

Procedure DOSCall; 

Procedure Work; 

end;

В этом фрагменте мы инкапсулировали в объект все методы, используемые обработчиком событий. Разумеется, необходимо соответствующим образом изменить заголовок процедуры FileOpen, поскольку она теперь стала методом объекта TNotebook:

Procedure TNotebook.FileOpen;

Аналогичным образом следует изменить и заголовки других инкапсулированных процедур. Теперь трансляция пройдет успешно, а после открытия файла станет недоступна команда F3.

Тексты двух других новых методов объекта TNotebook не нуждаются в особых комментариях:

Procedure TNotebook.FileSave; 

{Закрывает файл данных} 

begin

Close(DataFile);

OpFileF := False;

EnableCommands(WinCom2); {Разрешаем открыть файл}

DisableCommands(WinComl) {Запрещаем работу и сохранение} 

end; {TNotebook.FileSave}

{-----------}

Procedure TNotebook.ChangeDir; 

{Изменяет текущий каталог} 

var

PD: PChDirDialog; {Диалоговое окно смены каталога/диска}

Control: Word; 

begin

New(PD, Init(cdNormal,0));{Создаем диалоговое окно}

Control := DeskTop^.ExecView(PD){Используем окно}

ChDir(PD^.Dirlnput^.Data^);{Устанавливаем новый каталог}

Dispose(PD, Done){Удаляем окно из кучи} 

end; {TNotebook.ChangeDir}

Несколько слов по поводу реализации процедуры TNotebook. ChangeDir. В ней используется объект TChDirDialog, входящий в модуль StdDlg. С помощью этого объекта создается диалоговое окно, позволяющее выбрать новый диск или каталог. После создания и использования экземпляра объекта TChDirDialog в его поле

Dirlnput^.Data^

устанавливается строка типа PathStr, задающая новый каталог (и, возможно, новый диск).

Чуть сложнее обстоит дело с процедурой DOSCall, которая должна реализовать временный выход в ДОС. Дело в том, что перед выходом необходимо сохранить в куче текущее состояние программы, а после возврата нужно восстановить состояние программы, в том числе и вид экрана. Чтобы реализовать имеющиеся в Turbo Vision средства сохранения и восстановления программы, в предложение Uses необходимо добавить ссылку на модуль Memory. Вот текст метода TNotebooLDOSCall:

Procedure TNotebook.DOSCall;

{Временный выход в ДОС} 

const

txt ='Для возврата введите EXIT в ответ'+' на приглашение ДОС...'; 

begin

DoneEvents;{Закрыть обработчик событий}

DoneVideo;{Закрыть монитор экрана}

DoneMemory;{Закрыть монитор памяти}

SetMemTop(HeapPtr) ;{Освободить кучу}

WriteLn(txt);{Сообщить о выходе}

SwapVectors;{Установить стандартные векторы}

{Передать управление командному процессору ДОС:}

Exec(GetEnv('COMSPEC'),''); 

{Вернуться из ДОС:}

SwapVectors; {Восстановить векторы) 

SetMemTop(HeapEnd); {Восстановить кучу} 

InitMemory; {Открыть монитор памяти}

InitVideo;{Открыть монитор экрана} 

InitEvents;{Открыть обработчик событий} 

InitSysError;{Открыть обработчик ошибок}

Redraw {Восстановить вид экрана} 

end; {DOSCall}

Процедуры DoneXXXX завершают работу отдельных частей Turbo Vision, а процедуры InitXXXXосуществляют обратные действия. С помощью процедуры SetMemTop в ДОС передается информация о фактически используемой динамической памяти (по умолчанию программе предоставляется вся доступная память). Этот вызов освобождает неиспользуемую в данный момент часть кучи для размещения в ней командного процессора COMMAND.COM. После возврата из ДОС вызов SetMemTop используется еще раз - для того, чтобы зарезервировать за программой всю ранее выделенную ей память. Процедура Redraw восстанавливает все видимые элементы экрана.

15.8. СОЗДАНИЕ И ИСПОЛЬЗОВАНИЕ ГРУПП

Пора заняться основной содержательной частью нашей программы - процедурой Work. Прежде всего следует продумать способ взаимодействия пользователя с данными (интерфейс пользователя). От удачного выбора интерфейса во многом зависит успех разработки диалоговых программ: неудобный способ доступа, связанный с необходимостью ввода каких-либо команд или ответов на многочисленные вопросы, надолго отобьет у пользователя всякое желание работать с программой. Turbo Vision предоставляет в Ваше распоряжение все необходимые средства для разработки современного объектно-ориентированного диалога. В ходе такого диалога пользователь видит на экране объекты, о которых идет речь, он может указать на любой объект и выбрать те действия, которые нужно осуществить над ним.

При работе с электронной записной книжкой хотелось бы, чтобы на экране появилось сразу несколько записей, отсортированных в алфавитном порядке. Пользователь должен иметь возможность «листать» книжку, отыскивать в ней нужную запись, добавлять новые и исключать ненужные записи, редактировать их (вносить изменения). Таким образом, ядром диалога должно стать окно с текстом. При необходимости пользователь может смещать текст в окне в ту или иную сторону, перемещать само окно относительно границ экрана, менять его размеры. Все эти возможности типичны для многочисленных текстовых редакторов, систем программирования, систем управления базами данных и т.п.

Для реализации этих действий в Turbo Vision предусмотрен специальный объект TWindow, экземпляры которого отображаются на экране в виде прямоугольного окна с рамкой и стандартными кнопками изменения размера и закрытия окна. Попробуем создать такое окно в нашей программе. Для этого изменим текст процедуры Work следующим образом:

Procedure TNotebook.Work; 

{Работа с данными}

var

R: TRect; 

begin

R.Assign(0,0,80,23);

Desktop^.Insert(New(PWindow,Init(R,'',0)))

end; {Work}

После запуска программы нажмите клавишу F3, укажите в диалоговом окне имя несуществующего файла (файл данных пока еще не создан), нажмите клавиши Enter и F4 - экран приобретет вид, показанный на рис. 15.4.

Если Ваш ПК оснащен устройством ввода типа мышь, Вы можете перемещать это окно по экрану (надо «схватить» мышью верхнюю рамку окна, т.е. подвести к ней указатель мыши, нажать левую кнопку и, удерживая кнопку нажатой, перемещать мышь), изменять его размеры («схватить» правый нижний угол), использовать стандартные кнопки изменения размера (справа на верхней рамке) и закрытия окна (слева). Ничего другого окно не умеет. А как загрузить в него текст? Как получить хорошо знакомые по среде Турбо Паскаль полосы-указатели и управлять с их помощью положением текста? Для этих целей можно было бы использовать объект TScroller, представляющий собой окно с текстом и с двумя полосами-указателями. Однако по умолчанию такое окно не имеет рамки, а потому не может изменять своего размера, в нем нет стандартных кнопок изменения размера и закрытия окна. Таким образом, и объект TScroller не решает всех проблем. Каков же выход? Нужно создать новый объект, объединяющий в себе свойства и TWindow, и TScroller! В терминах Turbo Vision такие составные объекты называются группами.

Рис. 15.4. Окно просмотра данных 

Введем в программу следующий объект:

type

PWorkWin =^TWorkWin; 

TWorkWin = object (TWindow) 

Constructor Init(Bounds: TRect); 

end;

Новый объект является потомком TWindow и, следовательно, наследует все свойства родителя, в том числе рамку и способность перемещения по экрану. Дополнительные свойства ему должен придать новый конструктор TWorkWin.Init, которому мы в качестве параметра передаем начальное положение и размеры создаваемого окна:

Constructor TWorkWin.Init(Bounds: TRect);

{Создание окна данных}

var

HS,VS: PScrollBar; {Полосы-указатели}

Interior: PScroller; {Указатель на управляемое текстовое окно} 

begin

TWindow.Init(Bounds,'',0); {Создаем новое окно с рамкой}

GetClipRect(Bounds){Получаем в BOUNDS координаты минимальной перерисовываемой части окна}

Bounds.Grow(-1,-1){Устанавливаем размеры окна с текстом}

{Включаем стандартные по размеру и положению полосы-указатели:}

VS := StandardScrollBar(sbVertical+sbHandleKeyBoard);

HS := StandardScrollBar(sbHorizontal+sbHandleKeyBoard); 

{Создаем текстовое окно:}

Interior := New(PScroller,Init(Bounds, HS, VS)); 

Insert(Interior) {Включаем его в основное окно} 

end; {TWorkWin.Init}

С помощью вызова процедуры GetClipRect мы получаем размеры минимального прямоугольника, который следует обновлять при любых перемещениях окна или изменениях его размера. Такой вызов позволяет до минимума сократить время вывода. Процедура Bounds.Grow изменяет вертикальный и горизонтальный размеры прямоугольника Bounds: при положительном параметре соответствующий размер увеличивается, при отрицательном - уменьшается. Параметры -1,-1 учитывают рамку основного окна. Функция StandardScrollBar создает указатель на управляющую полосу стандартного размера. При обращении к ней параметр sbVertical (sbHorizontal) определяет положение полосы, а параметр sbHandleKeyboard разрешает использование клавиатуры для управления ею (если этот параметр не включить, полоса будет управляться только с помощью мыши). Наконец, процедура Insert включает вновь созданное окно TScrollBar в основное окно TWindow, так что теперь оба окна будут функционировать как одно целое.

Для создания группы необходимо в объект-потомок от TGroup (обычно - это объект TWindow или потомок от него) вставлять нужные элементы с помощью метода Insert.

Осталось лишь нужным образом изменить процедуру Work:

Procedure TNotebook.Work;

{Работа с данными}

var

R: TRect;

PW: PWorkWin; 

begin

R.Assign(0,0,80,23) ;

PW := New(PWorkWin, Init(R));

DeskTop^.Insert(PW) 

end; {Work}

Если исполнить подготовленную таким образом программу, на экране появится изображение, показанное на рис. 15.5.

Рис.15.5. Окно с полосами прокрутки

15.9. ВЫВОД ТЕКСТА

По сравнению с рис. 15.4 мы добились немногого, ведь пока еще не решена главная проблема - вывод нужного текста. Разумеется, в Вашем распоряжении всегда имеется процедура WRITELN, однако вывод текста «в лоб» с помощью этой процедуры практически никогда не используется в Turbo Vision, так как в этом случае выведенный текст не будет связан с окнами.

В объекте TScroller для вывода текста предусмотрен абстрактный метод Draw. Абстрактным он называется потому, что не выполняет никакой полезной работы. Однако именно к этому методу обращается обработчик событий объекта TScroller всякий раз, когда понадобится обновить на экране вид окна. Чтобы объект выполнял все заложенные в него функции, нам необходимо перекрыть этот метод новым. Мы уже знаем, что для этого нужно объявить новый объект:

type

PInterior =ATInterior; TInterior = object (TScroller)

Constructor Init(var Bounds: TRect; HS,VS: PScrollBar);

Procedure Draw; virtual;

Procedure ReadFile; 

end;

Мы перекрыли абстрактный метод Draw, стандартный конструктор Init и инкапсулировали в объект новый метод ReadFile. Новый конструктор предназначен для инициации экземпляра объекта TScroller. Кроме того, с помощью метода ReadFile он должен прочитать все записи файла данных и подготовить соответствующий массив строк - это сократит время на обновление текста процедурой Draw.

Перед тем, как двигаться дальше, подумаем о способе хранения строк для процедуры Draw. Если все необходимые действия по чтению нужной записи из файла и преобразования ее к текстовому формату возложить на процедуру Draw, наша программа станет слишком медленной, в особенности, если файл данных записан на дискете. Поэтому предусмотрим такие глобальные переменные:

const

MaxLine = 300; {Максимальная длина массива} 

LLine = LName+LPhone+LAddr; {Длина строки} 

var

NLines: Word; {Истинная длина массива строк} 

Lines: array [1..MaxLine] of String [LLine]; {Массив строк}

Теперь нетрудно подготовить процедуру ReadFile:

Procedure TInterior.ReadFile;

{Читает содержимое файла данных в массив Lines}

var

k: Integer; s: String;

Data: DataType; 

begin

seek(DataFile, 0) ;

NLines := FileSize(DataFile);

if NLines > MaxLine then

NLines := MaxLine; 

for k ':= 1 to NLines do 

begin

Read(DataFile, data); 

with data do 

begin

s := Name;

while Length(s) < LName do

s := s+' '; 

s := s+Phone; 

while Length(s) < LName+LPhone do

s := s+' '; 

s := s+Addr 

end;

Lines[k] := s 

end; 

end; {ReadFile}

В этой процедуре из записей файла данных готовится массив строк Lines, причем начало каждого поля выравнивается так, чтобы поля образовали колонки - такая форма вывода поможет легко найти на экране каждое поле.

Теперь займемся процедурой Draw:

Procedure TInterior.Draw; 

{Выводит данные в окно просмотра}

var

n, {Текущая строка экрана} 

k: Integer; {Текущая строка массива}

В: TDrawBuffer; Color: Byte; 

begin

Color := GetColor(l); {Использовать цвет основного текста} 

for n := 0 to pred(Size.Y) do 

{Size.Y - количество строк окна} 

begin

k := Delta.Y+n+1; {Delta.Y - номер первой выводимой строки} 

MoveChar(B,' ',Color,Size.X);

MoveStr(B, Copy(Lines[k],Delta.X+l,Size.X),Color); 

WriteLine(0,N,Size.X,l,B) 

end 

end; {TInterior.Draw}

Работа процедуры основана на использовании текущих размеров и положения текстового окна относительно текста. Эти параметры хранятся в полях Size и Delta объекта TScroller и обновляются всякий раз, когда пользователь манипулирует полосами управления или изменяет размеры окна. Для вывода текста используются три процедуры: MoveChar, MoveStr, WriteLine. Каждая из них оперирует переменной В типа TDrawBuffer, представляющей собой последовательности кодов выводимых символов и их атрибутов. Процедура MoveChar заполняет переменную В указанным символом (' ') и атрибутом (Color). Процедура MoveStr копирует строку в переменную В, а с помощью WriteLine осуществляется вывод буфера В на экран.

Для вывода изображений (текста) перекрывайте и используйте метод Draw объекта-владельца нужной части экрана. Это обеспечит автоматическое изменение изображения и его прорисовку при изменении границ или положения поля вывода.

15.10. ЦВЕТОВАЯ ПАЛИТРА

В процедуре Draw переменная Color задает атрибуты (цвет символов и цвет фона) символов, выводимых с помощью методов MoveChar и MoveStr. С помощью функции GetColor она устанавливается таким образом, чтобы символы на экране отображались цветовым сочетанием с номером 1. В Turbo Vision используется гибкая система установки цвета отдельных видимых элементов. Элемент изображения связывается не с каким-то конкретным цветом, а с индексом в таблице цветов, называемой палитрой. Количество элементов палитры зависит от количества цветовых сочетаний, используемых при выводе элемента изображения. Например, в TScroller используется двухэлементная палитра цветов: первый элемент устанавливает цвет нормального текста, второй - выделенного текста (рис. 15.6).

Рис.15.6. Палитра объекта TScroller

Числа 6 и 7 в этой палитре указывают не конкретные цвета, а номера позиций в палитре объекта-владельца. Для нашего случая объектом-владельцем будет TWindow. Таким образом, цвет номер 1 палитры TScroller лишь указывает на шестое по счету цветовое сочетание в палитре TWindow (рис. 15.7).

Рис. 15.7. Связь палитр TScroller и TWindow

Шестой элемент палитры TWindow в свою очередь ссылается на 13-й элемент палитры своего владельца - TProgram. Объект TProgram - это начальный видимый элемент любой программы в Turbo Vision. На нем заканчивается любая цепочка ссылок, т.е. его палитра содержит конкретные атрибуты символов. Тринадцатый элемент этой палитры содержит значение $1Е, что соответствует выводу желтого символа на синем фоне - именно таким образом отображается нормальный текст в окне TScroller, если это окно вставлено в TWindow. Если бы объект TScroller был помещен непосредственно на панель экрана, то значение 6 в первом элементе палитры TScroller указывало бы на 6-й элемент палитры TProgram, содержащий атрибуты $28 (темно-серые символы на зеленом фоне). Цветовые палитры в Turbo Vision содержат такие значения по умолчанию, чтобы любая комбинация цветов давала приятную цветовую гамму. При необходимости пользователь может изменить любую цветовую палитру. Вернемся к нашему примеру и рассмотрим реализацию конструктора Init:

Constructor TInterior.Init(var Bounds: TRect;

HS,VS: PScrollBar); 

{Создает окно для данных} 

begin

Inherited Init(Bounds, HS, VS) ;

ReadFile;

GrowMode := gfGrowHiX + gfGrowHiY;

SetLimit(LLine,NLines) 

end; {Tinterior.Init}

Объект TScroller имеет поле GrowMode, которое определяет, как элемент будет изменять свои размеры, если пользователь потребует этого. Параметр gfGrowHiX предписывает окну TScroller изменяться таким образом, чтобы правая его граница всегда находилась на постоянном расстоянии от правой границы владельца. Точно также gfGrowHiY задает неизменным расстояние нижней границы окна TScroller от нижней границы владельца. Таким образом, окно TScroller всегда будет занимать всю внутреннюю часть окна-владельца TWindow. С помощью процедуры SetLimit (X, Y) мы задаем горизонтальную Х и вертикальную У границы перемещения окна относительно текста. Эти границы будут выдерживаться при управлении окном с помощью клавишей или мыши: какими бы не были текущие размеры окна, нажатие на клавишу End, например, смещает его вправо так, чтобы самым правым видимым символом был Х-й символ текста. Нажатие на клавиши Ctrl-PgDn смещает окно вниз по тексту таким образом, чтобы самая нижняя строка окна соответствовала Y-й строке текста. Иными словами, параметры X и Y задают координаты правого нижнего угла виртуального (воображаемого) экрана неограниченных размеров, на котором находится текст и по которому «скользит» окно. Левый верхний угол виртуального экрана всегда имеет координаты (0,0).

Осталось отредактировать конструктор TWorkWinJnit: нужно изменить тип переменной Interior

var

.......

Interior: PInterior; 

и обращение к конструктору:

Interior := New(PInterior, Init(Bounds, HS, VS));

Но не спешите запускать программу на счет: ведь файла данных пока еще нет, а поэтому Вы ничего не увидите на экране. Чтобы все-таки оценить достигнутые результаты, измените текст процедуры ReadFile - добавьте в него следующие строки:

Procedure TInterior.ReadFile; 

{Читает содержимое файла данных}

 var

.......

f: text;

begin

s := copy(ParamStr(0),l,pos('.',ParamStr(0)))+'pas'; 

assign(f,s);

reset(f); {Открываем файл с текстом программы} 

NLines := 0;

while not EOF(f) and (NLines < MaxLine) do 

begin

inc(NLines);

ReadLn(f/Lines[NLines]) 

end;

close (f) ; 

exit;

.......

end; {ReadFile}

Добавленные строки заставят процедуру прочитать в массив Lines текст самой программы (если Вы будете запускать программу из среды Турбо Паскаль, не забудьте установить компиляцию в дисковый файл опцией COMPILE/DESTINATION, иначе оператор

s:=copy(ParamStr(0),l,pos('.',ParamStr(0)))+'pas';

не сможет установить в S правильное имя файла с текстом Вашей программы). После запуска программы нажмите клавишу F3, задайте имя несуществующего файла, нажмите клавиши Enter и F4 - на экране появится изображение, показанное на рис. 15.8.

Это окно откликается на нажатие клавиш управления курсором, команды PgUp, PgDn, Ctrl-PgUp и т.д. подобно тому, как ведет себя окно редактора в среде Турбо Паскаль. С помощью мыши Вы можете перемещать его по экрану, изменять размеры, закрывать - все эти действия реализует стандартный обработчик событий объекта TScroller.

15.11. ИСПОЛЬЗОВАНИЕ КОЛЛЕКЦИЙ

Для вывода текста мы использовали глобальный массив Lines. Как известно, длина любого массива в Турбо Паскале не может превышать длину сегмента данных (64 Кбайт). Это ограничение можно убрать, если воспользоваться еще одним механизмом Turbo Vision - коллекциями. Подобно массивам, коллекции представляют собой набор элементов, в которых можно хранить любые данные, включая экземпляры любых объектов. К элементам коллекции можно обращаться по индексу, однако, в отличие от массива, коллекция размещается в куче, поэтому ее суммарная длина ограничивается всей доступной памятью и может быть больше 64 Кбайт. Кроме того, размер коллекции не лимитируется при ее создании и может динамически изменяться в процессе работы программы.

Рис.15.8. Окно с текстом программы.

Коллекции обладают целым рядом новых свойств. В частности, к любой коллекции можно применить метод ForEach, который осуществит заданные Вами действия над каждым элементом коллекции. Таким способом можно, например, быстро отыскать элемент, удовлетворяющий заданным требованиям. Наконец, в Turbo Vision определены отсортированные коллекции, элементы которых упорядочиваются по заданному ключу. Все это делает коллекции более предпочтительным способом хранения данных, чем массивы Турбо Паскаля.

Попробуем заменить массив Lines на отсортированную коллекцию. Введем в объект TInterior новое поле PS:

type

TInterior = object (TScroller) 

PS: PStringCollection;

.......

end;

Тип PStringCollection в Turbo Vision определен как указатель на экземпляр объекта TStringCollection, представляющий собой отсортированную коллекцию строк. Сортировка строк осуществляется по обычным правилам сравнения строк по ASCII-кодам. Если вновь помещаемая строка уже существует в коллекции, она не дублируется (при желании программист может разрешить дублирование одинаковых строк), поэтому в общем случае количество элементов коллекции может оказаться меньшим количества помещенных в нее строк.

Для создания коллекции удалите ненужные теперь глобальные объявления MaxLine, Lines и NLines (в коллекции есть другие средства доступа к элементам) и измените метод ReadFile следующим образом :

Procedure TInterior.ReadFile;

var

.....

begin

PS := New(PStringCollection, Init(100,10)); 

s := copy(ParamStr(0),1,pos('.',ParamStr(0)))+'pas'; 

assign(f,s);

reset (f); {Открыть файл с текстом программы} 

while not (EOF(f) or LowMemory) do 

begin

ReadLn(f,s);

if s <> ' ' then PS^.Insert(NewStr(s)) 

end;

Close(f); 

exit;

Seek(DataFile,0);

while not (EOF(DataFile) or 

LowMemory) do 

begin

Read(DataFile, data); 

with data do 

begin

end;

if s<>''then PS^.Insert(NewStr(s)) 

end; 

end; {ReadFile}

В приведенном фрагменте мы предусмотрительно изменили только ту часть программы, которая стоит после оператора Exit и которая зависит от удаленных глобальных определений. Вы должны сделать эти изменения (они все равно нам пригодятся) или закомментировать эту часть текста, чтобы получить синтаксически правильный вариант программы.

С помощью оператора

PS := New(PStringCollection, Init(100,10));

инициируется экземпляр коллекции, причем параметр 100 определяет начальный размер коллекции, а параметр 10 - шаг наращивания коллекции, если ее размер превысит 100 элементов. Оператор

if s<> ' ' then PS^.Insert(NewStr(s))

вставляет очередную непустую строку в коллекцию. Заметим, что коллекции РЗЛ передается не строка 5, а лишь указатель на нее, т.к. функция NewStr размещает строку в куче и возвращает ее адрес. Функция NewStr не может разместить в куче пустую строку, поэтому мы вставляем в коллекцию только непустые строки.

Функция LowMemory используется для контроля за размерами динамической памяти: она возвращает значение True, если в куче осталось менее 4 Кбайт.

В последний оператор метода Interior.Init внесите следующее изменение:

Constructor TInterior.Init(var Bounds: TRect; HS,VS: PScrollBar);

begin

SetLimit(LLine,PSA.Count) 

end; {TInterior.Init}

Другим станет также и реализация метода TInterior.Draw:

Procedure TInterior.Draw; 

var

n,k: Integer; 

B: TDrawBuffer; 

p: PString; 

Color: Byte; 

begin

Color := GetColor(1); 

for n := 0 to pred(Size.Y) do 

begin

k := Delta.Y+n; 

MoveChar(B,' ',Color,Size.X); 

if k < pred(PS^.Count) then 

begin

p := PS^.At(k);

MoveStr(B,Copy(р^,Delta.X+1,Size.X),Color) 

end;

WriteLine(0,N,Size.X,1,B) 

end 

end; {TInterior.Draw}

Элементы коллекции нумеруются, начиная с номера 0. Длина коллекции (общее количество ее элементов) хранится в поле PS^. Count. Функция PS^.At(k) возвращает указатель на k-й элемент коллекции.

Созданная коллекция размещается в динамической памяти, поэтому после использования ее следует удалить из кучи. Для этого перекроем стандартный деструктор Done:

type

TInterior = object (TScroller)

.......

Destructor Done; Virtual; 

end;

Destructor TInterior.Done; 

begin

Dispose(PS, Done); {Удаляем коллекцию} 

Inherited Done {Выполняем стандартный деструктор} 

end;

Еще раз хочу обратить Ваше внимание на особенность программирования в среде Turbo Vision: Вы определяете метод, но не указываете, когда он должен быть выполнен. Правильно сконструированный объект уже «знает», когда он ему понадобится! Так было в случае правила Draw, так же обстоит дело и с деструктором Done: обработчик событий окна TWindow вызовет этот метод, как только он получит событие cmCancel (закрыть окно). Чтобы убедиться в этом, установите контрольную точку в строке

Dispose(PS, Done); {Удаляем коллекцию}

и запустите программу. Останов в контрольной точке произойдет только в том случае, если Вы загрузите окно с текстом и попытаетесь выйти из программы. Если из программы выйти сразу после ее запуска, контрольная точка не сработает.

Вид экрана с окном просмотра отсортированного файла показан на рис. 15.9.

Puc.15.9. Окно с отсортированным тестом программы

15.12. УКАЗАТЕЛЬ НА ЭЛЕМЕНТ СПИСКА

Как уже отмечалось, с помощью процедуры Draw можно выводить обычный текст и выделенный текст. Попробуем использовать это обстоятельство для того, чтобы поместить в окно просмотра указатель на текущий элемент данных. Для этого добавим в TInterior еще одно поле:

type

TInterior = object (TScroller) 

Location: Word;

.......

end;

Поле Location будет хранить номер той строки,которая отождествляется с выбранной строкой и которая на экране должна выделяться цветом.Добавьте в конце метода ReadFile строку

Location:=0;

и измените метод Draw:

Procedure TInterior.Draw; 

{Выводит данные в окно просмотра} 

var

n,k: Integer; 

В: TDrawBuffer; 

р: PString; 

Color: Byte; 

begin

if Delta.Y > Location then

Location := Delta.Y;

if Location > Delta.Y+pred(Size.Y) then

Location := Delta.Y+pred(Size.Y); 

for n := 0 to pred(Size.Y) do 

begin

k := Delta.Y+n; 

if k=Location then

Color := GetColor(2) 

else

Color := GetColor(1);

end 

end; {TInterior.Draw}

Вначале проверяется, попадает ли строка с номером, хранящимся в Location, в число выводимых строк. Если это не так, значит пользователь изменил размеры окна или сдвинул его относительно текста; в этом случае нужным образом корректируется значение Location. Такая проверка гарантирует, что в окне всегда будет выводиться текущая строка. Перед выводом очередной строки сравнивается значение ее номера с величиной Location и, если величины совпадают, строка выводится цветом 2 из палитры TScroller (темно-синими символами на сером фоне).

Создав указатель в окне, нужно предусмотреть и средства воздействия на него. Для этого нам понадобится проверять действия пользователя с мышью и клавиатурой и изменять положение указателя. Вы не забыли, что все действия программы в Turbo Vision выполняются с помощью обработчика событий? Перекроем стандартный метод HandleEvent в объекте TInterior:

type

TInterior,. = object (TScroller)

.......

Procedure HandleEvent(var Event: TEvent); Virtual; 

end;

Procedure TInterior.HandleEvent(var Event: TEvent); 

{Обработчик событий для окна данных} 

var

R: TPoint; 

begin

Inherited HandleEvent(Event);

case Event.What of evMouseDown: {Реакция на щелчок мышью} 

begin

MakeLocal(MouseWhere, R){Получаем в R локальные координаты указателя мыши}

Location := Delta.Y+R.Y; 

Draw 

end;

evKeyDown: {Реакция на клавиши + -} 

case Event.KeyCode of

kbGrayMinus: if Location > Delta.Y then 

begin

dec(Location); 

Draw 

end;

kbGrayPlus: if Location < Delta.Y+pred(Size.Y) then 

begin

inc(Location); 

Draw

end; 

end 

end 

end; {TInterior.HandleEvent}

В новом методе вначале вызывается унаследованный обработчик событий TScroller.HandleEvent, с помощью которого обрабатываются все стандартные действия с окном (смещение текста, изменение размеров и т.д.). Затем обрабатываются события от нажатия кнопки мыши и от нажатия клавиш «+» и «-» из зоны цифровых клавиш (на клавиатуре ПК они выделяются серым цветом). С клавишей «+» связывается действие «Сместить указатель вниз на одну строку», с клавишей «-» - «Сместить вверх». Выбор серых клавиш «+» и «-» для смещения указателя вызван тем, что клавиши управления курсором используются для смещения окна и обрабатываются стандартным обработчиком событий. Заметим, что нажатие кнопки мыши будет обрабатываться в TScroller.HandleEvent только в том случае, если указатель мыши находится на рамке окна или на полосах управления. Если указатель сместить внутрь окна, нажатие на кнопку мыши будет преобразовано в событие evMouseDone и передано в наш обработчик. В этом случае глобальная переменная MouseWhere содержит абсолютные координаты указателя мыши (т.е. координаты относительно левого верхнего угла экрана). Чтобы получить номер соответствующей строки текста, мы сначала с помощью оператора

MakeLocal(MouseWhere, R) ;

получаем в переменной R локальные координаты мыши относительно границ окна TScroller. Оператор

Location := Delta.Y+R.Y;

устанавливает в поле Location номер той строки текста, на которой располагается указатель мыши.

15.13. ДИАЛОГОВОЕ ОКНО ВЫБОРА РЕЖИМА

Подведем некоторые итоги. Мы создали программу, которая погружает пользователя в среду объектно-ориентированного диалога Turbo Vision: она поддерживает командные клавиши, работу с мышью, может сменить каталог или диск, выбрать нужный файл и загрузить его в окно просмотра. Не так плохо для 300 строк программного текста! Наша дальнейшая задача - реализовать другие режимы работы (поиск нужной строки, добавление и уничтожение строк, их изменение). Для двух из них (уничтожение и редактирование строки) в программе необходимо каким-то образом указать ту строку, с которой будет работать пользователь. Мы уже реализовали эту возможность, предусмотрев в окне просмотра текста управляемый указатель. Поэтому режим просмотра можно принять в качестве основного режима работы с данными. В связи с этим следует несколько изменить метод TNotebook-HandleEvent, предусмотрев в нем автоматический переход в режим просмотра данных в случае успешного открытия файла с данными:

Procedure TNotebook.HandleEvent(var Event: TEvent); 

{Обработчик событий программы} 

begin

Inherited HandleEvent(Event); 

if Event.What = evCommand then 

case Event.Command of 

cmOpenFile: 

begin

FileOpen;

if OpFileF then Work 

end;

.......

end; {TNotebook.HandleEvent}

Как из режима просмотра данных перейти к другим режимам? Возможно несколько решений. Я предлагаю для этих целей воспользоваться командой cmClose (закрыть окно просмотра): в момент, когда пользователь в режиме просмотра данных нажмет клавишу Esc или воздействует мышью на кнопку «Закрыть окно», на экране должно раскрыться диалоговое окно выбора режима, предлагающее одно из пяти возможных продолжений:

Для реализации этой идеи в уже созданный нами обработчик событий TInterior.HandleEvent следует ввести обработку события cmClose:

const

{Команды для обработчиков событий:}

.......

cmCan=205; 

cmDelete=206;

cmSearch = 207;

cmEdit = 208;

cmAdd = 209;

Function Control: Word; {Создает и использует диалоговое окно выбора режима работы) 

begin

Control := cmCan 

end; {Control}

{-----------------}

Procedure TInterior.HandleEvent (var Event: TEvent) ;

{Обработчик событий для окна данных}

Procedure DeleteItem;

{Удаляет указанный в Location элемент данных}

begin

end; {DeleteItem}

{-----------------}

Procedure AddItem(Edit: Boolean);

{Добавляет новый или редактирует старый элемент данных}

begin

end; {AddItem}

{-----------------}

Procedure SearchItem;

{Ищет нужный элемент}

begin

end; {SearchItem}

{-----------------}

var

R: TPoint; label Cls;

begin {TInterior.HandleEvent} 

Inherited HandleEvent (Event) ; 

case Event. What of evCommand:

case Event . Command of 

cmClose: 

begin

Cls:

case Control of{Получить команду из основного диалогового окна}

cmCan,

cmCancel:EndModal (cmCancel) ;

cmEdit:AddItem (True);

cmDelete:DeleteItem;

cmSearch:SearchItem;

cmAdd:AddItem (False);

end

end;

cmZoom: exit;

end;

evMouseDown: {Позиционировать мышью}

.....

evKeyDown: {Позиционировать клавишами + -}

case Event.KeyCode of

kbEsc: goto Cls;

kbGrayMinus: if Location > Delta.Y then

.....

end; {TInterior.HandleEvent}

В этом фрагменте мы расширили набор нестандартных команд (константы стпСап, ..., cmAdd), ввели новую функцию Control и предусмотрели необходимые процедуры в теле обработчика событий. Заметим, что режимы редактирования записи и добавления новой записи очень схожи по организации диалога с пользователем, поэтому он» реализуются в рамках одной процедуры AddItem и управляются параметром обращения к ней.

Функция Control используется для создания диалогового окна выбора продолжения. В качестве значения этой функции будет возвращаться одна из пяти новых команд. В начальном варианте функция возвращает команду стСап, что интерпретируется обработчиком событий как указание на завершение работы с диалоговым окном. Поэтому, если Вы вставите указанный текст в программу и запустите ее, поведение программы останется прежним.

Займемся реализацией функции Control. Она должна создать диалоговое окно выбора режима, получить с его помощью команду, идентифицирующую выбранный режим, и вернуть эту команду в качестве своего значения:

Function Control: Word;

{Получает команду из основного диалогового окна} 

const 

X = 1; 

L = 12; 

DX= 13;

But: array [0. .4]of String [13] = {Надписи на кнопках:}

('~1~ Выход ','~2~Убрать ','~3~ Искать ',

'~4~ Изменить ','~5~ Добавить ') ; 

Txt: array [0..3]of String [52] = (

{Справочный текст:}

'Убрать - удалить запись, выделенную цветом', 'Искать - искать запись, начинающуюся нужными буквами', 'Изменить - изменить поле (поля) выделенной записи', 'Добавить - добавить новую запись'); var

R: TRect; 

D: PDialog; 

k: Integer;

begin 

R.Assign(7,6,74,15) ;

D := New{PDialog,Init(R, 'Выберите продолжение:')); 

with D^ do 

begin

for k := 0 to 3 do {Вставляем поясняющий текст} 

begin

R.Assign(l,l+k,65,2+k);

Insert(New(PStaticText,Init(R,#3+Txt[k]))) 

end;

for k := 0 to 4 do {Вставляем кнопки:} 

begin

R.Assign(X+k*DX,6,X+k*DX+L,8); 

Insert(New(PButton, Init(R,But[k],cmCan+k,bfNormal))) 

end;

SelectNext(False); {Активизируем первую кнопку} 

end;

Control := DeskTopA.ExecView(D); {Выполняем диалог} 

end; {Control}

Сначала создается диалоговое окно с заданными размерами (чтобы программе стал доступен тип TDialog, укажите в предложении Uses модуль Dialogs). Затем в цикле

for k := 0 to 3 do

в окно вставляется поясняющий текст (см. рис.15.10).

Рис.15.10. Диалоговое окно функции Control

Этот текст не связан с диалогом и называется статическим. Для вставки статической строки в любой видимый элемент используется конструктор TStaticTextJnit, которому в качестве параметров передаются координаты строки и сама строка. Как Вы уже могли заметить, идентификаторы объектов в Turbo Vision начинаются на букву Т, а идентификаторы типов-указателей на экземпляры этих объектов начинаются на букву Р. Таким образом, PStaticText - это тип-указатель на экземпляр объекта TStaticText, поэтому оператор

Insert(New (PStaticText, Init(R,'Текст'))

помещает строку «Текст» на место, заданное координатами переменной R. Отметим, что если строка начинается на символ #3, то при выводе на экран она будет размещаться в центре прямоугольника R. Мы используем это соглашение и дополняем каждую выводимую строку этим символом. В цикле

for k := 0 to 4 do {Вставить кнопки:}

в окно вставляются пять кнопок. При их инициации используется то обстоятельство, что определенные нами команды cmCan, ..., cmAdd образуют непрерывное множество [205..209].

Особо следует остановится на операторе

SelectNext(False); {Активизируем 1-ю кнопку}

Дело в том, что по умолчанию активизируется тот элемент диалогового окна, который задан (вставлен в окно) последним. Чтобы изменить активность по умолчанию, используется вызов процедуры SelectNext, которая смещает активность к следующему элементу. Так как элементы образуют замкнутую цепь (от последнего элемента активность переходит к первому), параметр обращения к этой процедуре указывает направления смещения: если он имеет значение False, активным станет следующий в цепи элемент, если True - предыдущий.

Прежде, чем Вы попробуете запустить эту программу на счет, внесем в нее несколько изменений. Во-первых, пора убрать имитацию данных, показываемых в окне просмотра. Для этого в процедуре TInterior.ReadFile необходимо удалить строки

s := copy(ParamStr(O),1,pos('.',ParamStr(0)))+'pas'; 

assign(f,s);

.....

exit;

Надеюсь, что Вы заблаговременно подготовили остальной текст этого метода, если это не так, вставьте операторы

seek (DataFile, 0);

while not (EOF (DataFile) or LowMemory) do 

begin

.....

end;

Location := 0

Во-вторых, обратили ли Вы внимание на то, что в процедуре TNotebook. Work указатель PW инициируется оператором

PW := New(PWorkWin, Init(R));

а динамическая память, выделенная для размещения экземпляра объекта TWorkWin, не возвращается обратно в кучу? Если да, то у Вас есть хорошие шансы избежать многих неприятностей при программировании в среде Turbo Vision. Конечно же, нам следовало где-то в программе позаботиться об удалении ненужного нам экземпляра объекта. Чтобы не усложнять программу, я не стал этого делать: если вставить оператор

Dispose(PW, Done) 

сразу за оператором

DeskTop^.Insert(PW)

то вновь созданное окно будет тут же удалено с экрана, поэтому оператор Dispose нужно разместить в обработчике событий TNotebook. HandleEvent (подумайте, где именно).

После включения диалогового окна в цепочку действий, связанных с инициацией PW, появилась возможность приостановить исполнение программы в процедуре Work: вместо оператора

DeskTop^.Insert(PW)

вставьте следующие строки:

Control := DeskTop^.ExecView(PW); 

Dispose(PW, Done)

и добавьте описание переменной Control:

var

.....

Control: Word;

В отличие от процедуры Insert процедура ExecView не только помещает видимый элемент на экран, но и приостанавливает дальнейшее исполнение программы Work до тех пор, пока не закончится диалог с пользователем.

И, наконец, еще одно усовершенствование. Работа с программой станет удобнее, если сразу после чтения файла с данными она перейдет к их показу. Реализовать это очень просто: добавьте вызов процедуры Work в процедуру FileOpen следующим образом:

Procedure TNotebook.FileOpen;

..... begin

.....

if OpFileF then 

begin

.....

Work{Переходим к работе} 

end;

.....

end; {FileOpen}

Если Вы внесете в программу все описанные изменения и запустите ее на счет , то при попытке выйти из режима просмотра на экране будет развернуто диалоговое окно, показанное на рис. 15.10. «Нажатие» на любую кнопку этого окна не приводит ни к каким последствиям - наше окно пока откликается только на стандартную команду cmClose, связанную с клавишей Esc.

Файл с данными DataType пока еще не существует. Чтобы программа смогла нормально работать, в диалоговом окне открытия файла укажите произвольное имя, например MYDATA. После завершения работы программы будет создан пустой файл MYDATA.DAT.

15.14. ОБРАБОТКА КОМАНД ПОЛЬЗОВАТЕЛЯ

Обработчик событий диалогового окна поддерживает только стандартные команды cmClose, cmOk, cmCancel. Как заставить его реагировать на команды пользователя? Вы правы: нужно перекрыть стандартный обработчик событий.

Введем новый объект TDlgWin как потомок объекта TDialog и перекроем его метод HandleEvent:

type

PDlgWin =ATDlgWin;

TDlgWin = object (TDialog)

Procedure HandleEvent(var Event: TEvent); Virtual; 

end;

В новом методе следует сначала вызвать стандартный обработчик, а затем проанализировать событие: если оно не очищено и содержит команду, значит была нажата какая-то командная кнопка, и нам следует заставить обработчик закрыть окно и вернуть эту команду как результат диалога с пользователем:

Procedure TDlgWin.HandleEvent(var Event: TEvent); 

{Обработчик событий для основного диалогового окна} 

begin

Inherited HandleEvent(Event);

if Event.What = evCommand then

EndModal(Event.Command) {Закрыть окно и вернуть команду} 

end;

Метод EndModal используется для того, чтобы завершить работу с диалоговым окном и вернуть команду в программу, использующую это окно. Измените описание переменной D в функции Control на

var

.....

D: PDlgWin;

и обращение к методу Init:

D := New(PDlgWin, Init(...));

и вновь запустите программу: теперь нажатие на любую кнопку диалогового окна приведет к его закрытию.

15.15. РЕДАКТИРОВАНИЕ И ДОБАВЛЕНИЕ ЗАПИСЕЙ

Для редактирования и добавления записей создадим окно, показанное на рис. 15.11.

Режим редактирования отличается от режима добавления записей двумя обстоятельствами: во-первых, в режиме редактирования поля ввода данных окна должны содержать текст, взятый из редактируемой записи, а в режиме ввода эти поля пусты. Во-вторых, режим редактирования завершается сразу после нажатия на клавишу Enter, в то время как в режиме ввода нажатие на эту клавишу означает добавление к файлу текущей записи и переход к вводу следующей: режим ввода завершается командой cmClose (клавиша Esc). С учетом этого оба режима реализуются в рамках одной процедуры AddItem (Edit), а параметр Edit указывает нужный режим: если Edit = True, реализуется режим редактирования, если False - режим добавления записей. Вот текст этой процедуры:

Рис.15.11. Окно ввода/редактирования записей

Procedure AddItem(Edit: Boolean);

{Добавляет новый или редактирует старый элемент данных}

const

у = 1; 

dy= 2;

L -= LName+LPhone+LAddr; 

var

Data: DataType;

R: TRect;

InWin: PDialog;

BName,BPhone,BAddr: PInputLine;

Control: Word;

OldCount: Word;

s: String;

р: PString; 

begin

Seek(DataFile,FileSize(DataFile));{Добавляем записи в конец файла}

repeat {Цикл ввода записей}

if Edit then {Готовим заголовок}

s := 'Редактирование:' 

else 

begin

Str(FileSize(DataFile)+1,s); 

while Length(s) < 3 do

s := '0'+s;

s :- 'Вводится запись N '+s 

end; 

FillChar(Data,SizeOf(Data),' ');{Заполняем поля пробелами}

R.Assign(15,5,65,16) ; 

InWin := New(PDialog, Init(R, s));{Создаем окно}

with InWin^ do 

begin{Формируем окно:}

R.Assign(2,y+1,2+LName,y+2);

BName := New(PInputLine, Init(R,LName));

Insert(BName); {Поле имени}

R.Assign(2,y,2+LName,y+1);

Insert(New(PLabel,

Init(R, 'Имя',BName)));

R.Assign(2,y+dy+1,2+LPhone,y+dy+2);

BPhone := New(PInputLine, Init(R,LPhone));

Insert(BPhone); {Поле телефона}

R.Assign (2,y+dy, 2+LPhone,y+dy+1) ;

Insert (New(PLabel,.

Init(R, 'Телефон',BPhone)));

R.Assign(2,y+2*dy+1,2+LAddr,y+2*dy+2);

BAddr := New(PInputLine, Init(R,LAddr));

Insert(BAddr); {Поле адреса}

R.Assign(2,y+2*dy,2+LAddr,y+2*dy+1); 

Insert(New(PLabel,

Init(R, 'Адрес',BAddr)));

{Вставляем две командные кнопки:}

R.Assign(2,y+3*dy+1,12,y+3*dy+3);

Insert(New(PButton,

Init(R, 'Ввести',cmOK,bfDefault)));

R.Assign(2+20,y+3*dy+1,12+20,y+3*dy+3);

Insert(New(PButton,

Init(R, 'Выход',cmCancel,bfNormal)));

SelectNext(False) {Активизируем первую кнопку} 

end; {Конец формирования окна} 

if Edit then with Data do

begin {Готовим начальный текст:}

р :=PS^.At(Location); {Читаем данные из записи)

S:=p^;

Name := copy(s,1,LName); 

Phone:= copy(s,succ(LName),LPhone); 

Addr := copy(s,succ(LName+LPhone),LAddr); 

InWin^.SetData(Data) {Вставляем текст в поля ввода} 

end;

Control := DeskTop^.ExecView(InWin); {Выполняем диалог} 

if Control=cmOk then with Data do 

begin

if Edit then

DeleteItem; {Удаляем старую запись} 

Name := BName^.Data^; 

Phone:= BPhone^.Data^; 

Addr := BAddr^.Data^; 

s[0] := chr(L) ; 

FillChar(s[1],L,' '); 

move(Name[1],s[1],Length(Name)) ; 

move(Phone[1],s[succ(LName)],Length(Phone)); 

move(Addr[1],s[succ(LName+LPhone)],Length(Addr)); 

OldCount := PS^.Count; {Прежнее количество записей} 

РS^.insert(NewStr(s)); {Добавляемв коллекцию} 

{Проверяем добавление} 

if OldCount <> РS^.Count then

Write(DataFile,Data) {Да - добавляем в файл} 

end

until Edit or (Control=cmCancel); 

Draw 

end; {AddItem}

Вначале указатель файла смещается в самый конец, подготавливая добавление записей (судя по всему, режим добавления будет использоваться гораздо чаще, чем режим редактирования). Затем формируется заголовок окна и само окно. Операторы

if Edit then with Data do

begin {Готовим начальный текст:}

.......

end;

готовят начальное состояние полей ввода в режиме редактирования. Оператор

InWin^. SetData (Data)

помещает подготовленный текст в нужные поля. При обращении к процедуре SetData данные должны быть предварительно подготовлены в строгом соответствии с порядком создания диалоговых полей в окне и типом их данных. Поскольку в нашем случае формат данных в полях ввода окна совпадает с форматом файловых данных, мы можем использовать одну и ту же переменную как для работы с файлом, так и для установки начальных значений диалоговых полей.

В самом общем случае пользователь должен объявить новый тип, соответствующий формату помещаемых в окно данных, и использовать выражение этого типа в качестве параметра обращения к процедуре SetData. Например, если бы в нашем окне было предусмотрено только одно поле ввода «Телефон», то установку данных можно было бы осуществить таким оператором:

InWin^. SetData (DataType . Phone)

где DataType.Phone - выражение типа String [LPhone].

Контроль за соответствием типа устанавливаемых данных порядку объявления и типу данных диалоговых полей полностью возлагается на программиста. В операторах

if Control=cmOk then with Data do 

begin

.....

end

данные, полученные из диалогового окна, помещаются сначала в отсортированную коллекцию, а затем - в файл. С помощью оператора

if OldCount <>PS^. Count then

проверяется изменение количества данных в коллекции (напомню, что в отсортированную коллекцию можно поместить только уникальную запись). Если количество записей в коллекции изменилось, значит новая запись не совпадает ни с одной из уже имеющихся и ее следует поместить в файл.

Операторы

if Edit then 

DeleteItem; {Удаляем старую запись}

предварительно удаляют старую запись с помощью обращения к процедуре DeleteItem.

15.16. УДАЛЕНИЕ ЗАПИСИ

При реализации режима удаления записи нам нужно учесть тот факт, что порядок следования записей в файле и коллекции может быть различным. Поэтому в процедуре DeleteItem организуется цикл поиска в файле удаляемой записи:

Procedure DeleteItem;

{Удаляет указанный в Location элемент данных}

var

D: Integer;

PStr: PString;

s: String;

Data: DataType; 

begin

PStr := PS^.At(Location){Получаем текущую запись}

s := copy(PSr^,1,LName)

seek(DataFile,0);

D := -1;{D - номер записи в файле}

repeat{Цикл поиска по совпадению поля Name:}

inc(D);

read(DataFile,Data);

with Data do while Length(Name) < LName do

Name := Name+' ' 

until Data.Name=s;

seek(DataFile,pred(FileSize(DataFile))); 

read(DataFile,Data); {Читаем последнюю запись} 

seek(DataFile,D);

write(DataFile,Data); {Помещаем ее на место удаляемой} 

seek(DataFile,pred(FileSize(DataFile))); 

truncate(DataFile); {Удаляем последнюю запись} 

with РS^ do D := IndexOf(At(Location)); 

PS^.AtFree(D); {Удаляем строку из коллекции} 

Draw {Обновляем окно} 

end; {DeleteItem}

15.17. РЕЖИМ ПОИСКА ЗАПИСИ

Для поиска нужной записи сформируем диалоговое окно, показанное на рис.15.12.

Рис.15.12. Окно ввода шаблона поиска

С помощью этого окна пользователь может задать несколько начальных букв, используемых как ключ для поиска записи. Получив данные из этого окна, процедура SearchItem организует поиск первой от начала коллекции строки, для которой не выполняется условие

Pattern >= Item

где Pattern - образец поиска, Item - текущая строка коллекции. Найденная строка указывается как текущая в поле Location и организуется вывод соответствующего текста в окне просмотра.

В реализации процедуры SearchItem указанная проверка осуществляется для строк, предварительно преобразованных к прописным буквам с помощью внутренней процедуры UpString, т.е. поиск игнорирует возможную разницу в высоте букв шаблона и строк коллекции.

Procedure SearchItem;

{Ищет нужный элемент}

Function UpString(s: String): String;

{Преобразует строку в верхний регистр}

var

k: Integer; begin

for k := 1 to Length(s) do 

if s[k] in ['a'-.'z'] then

s[k] := chr(ord('A')+ord(s[k])-ord('a')) 

else if s[k] in ['a'..'n']. then

s[k] := chr(ord('A')+ord(s[k])-ord('a')) 

else if s[k] in ['р'..'я'] then

s[k] := chr(ord('P')+ord(s[k])-ord('p')); 

UpString := s 

end; {UpString} 

var

InWin: PDialog; 

R: TRect; 

s: String; 

p: PInputLine; 

k: Word; 

begin {SearchItem}

R.Assign(15,8,65,16); 

InWin := New(PDialog, 

Init(R,'Поиск записи:')); 

with InWin^ do 

begin

R.Assign(2,2,47,3);

p := New(PInputLine, Init(R,50));

Insert(p);

R.Assign(l,l,40;2);

Insert(New(PLabel, Init(R, 'Введите образец для поиска:',р))); 

R.Assign(10,5,20,7); 

Insert(New(PButton, Init(R,'Ввести',cmOk,bfDefault))); 

R.Assign(25,5,35,7); 

Insert(New(PButton, Init(R,'Выход',cmCancel,bfNormal))); 

SelectNext(False) 

end; 

if DeskTop^.ExecView(InWin) = cmCancel then

exit; s := p^.Data^;

Location := 0;

while (UpString(s) >= UpString(PString(PS^.At(Location))^))

and (Location < pred(PS^.count)) do 

inc(Location); if (Location < Delta.Y) or

(Location > Delta.Y+pred(Size.Y)) then 

ScrollTo(Delta.X,Location) 

else

Draw 

end; {SearchItem}

15.18. ИТОГИ

Итак, мы завершили создание диалоговой программы, обслуживающей электронную «записную книжку». В ходе ее реализации Вы познакомились с некоторыми возможностями диалоговой среды Turbo Vision. Я не ставил себе целью дать здесь подробное описание всех или даже использованных в программе средств Turbo Vision -этому посвящены остальные главы этой части книги. В частности, вне рамок примера остались такие важные механизмы, как потоки, ресурсы, препроцессорные и постпроцессорные события и многие другие возможности Turbo Vision. Однако уже рассмотренные средства свидетельствуют о том, что программирование в Turbo Vision существенно отличается от традиционных методов создания программ, и это отличие является следствием широкого использования в Turbo Vision механизмов объектно-ориентированного программирования.

Наша программа, насчитывающая всего около 600 строк, обеспечивает весьма высокий уровень диалога с пользователем: в ней используются командные клавиши, «выпадающие» меню, удобные диалоговые окна, поддержка мыши. Думаю, что вряд ли каким-либо другим способом мы смогли бы создать столь сложную диалоговую среду программой такого объема. Таким образом, Turbo Vision является превосходным инструментом для разработки диалоговых программ, ориентированных на текстовый режим работы экрана. Использование этой среды в Ваших программах резко сократит сроки их создания и повысит качество.

Разумеется, созданная программа далека от совершенства, однако даже в этом виде она, как показывает мой собственный опыт, может быть достаточно полезной. При желании ее можно взять за основу создания более сложной информационно-поисковой системы.